iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 3
0
自我挑戰組

玩轉 React 從0到1系列 第 24

【Day 24】用 SOLID 方式開發 React (1)

  • 分享至 

  • xImage
  •  

前言

在 OOP 的世界裡,我們常常會聽到高內聚(Cohesion),低耦合(Coupling),以及 SOLID 原則,而且 Design Pattern 都是從 SOLID 原則來的,這些名詞的目的都是為了讓我們的程式碼降低壞味道~~但 Javascript 作為一個弱型別的語言,它跟 JAVA, C# 的 SOLID 原則還是有所不同,不過 SOLID 作為開發的通用規則,使用在 Javascript 上還是能有很好的效果

SOLID

單一職責原則(SRP , Single Responsibility Principle)

每個物件的成員最好只負責一件事,不要給予太多的責任

舉例而言:
下方程式是在說明,有一個 ScoreCalculate 掌管成績計算的功能,原始為 100 分,錯一次扣 10 分,60分及格

class ScoreCalculate {
  constructor() {
    this.score = 100;
  }
  minus() {
    this.score -= 10;
  }
  checkIfPassTest() {
    if (this.score >= 60) {
      console.log('通過考試');
    } else {
      console.log('沒過啊~要重修');
    }
  }
}
const score = new ScoreCalculate;
score.minus();
score.checkIfPassTest();

這原則意味著,如果擔的責任越多就越具有高耦合性,以至於後續難以擴展修改,以上面例子來說,很明顯 ScoreCalculate已經掌管超出計算範圍的功能,還需要另外去判斷是否通過考試,因此應該將通過考試這個職責拆分成另外一個單獨的 class
舉例而言,更改後程式碼

class ScoreCalculate {
  constructor() {
    this.score = 100;
  }
  minus() {
    this.score -= 10;
  }
}
class ScorePassAlert {
  checkIfPassTest(s) {
    if (s.score >= 60) {
      console.log('通過考試');
    } else {
      console.log('沒過啊~要重修');
    }
  }
}
const s = new ScoreCalculate;
s.minus();
console.log(s.score);
const passOrNot = new ScorePassAlert;
passOrNot.checkIfPassTest(s);

這樣的改法,會使得每個元件都可以複用,而且在修改邏輯的時候不影響其他邏輯
而在 React 當中,也有使用 HOC(Higher Order Component) 的高階做法(後續會單獨介紹)

開放封閉原則 (OCP, Open-Close Principle)

物件本身如果已經穩定了,就不應該對已經穩定的程式去做異動

舉例而言:
下方程式是在說明,有一個 productAllSort 的 Object 具有獲取商品陣列中特定分類的功能(getSort)

let productSorts = ['3C產品', '食品'];
let productAllSort = {
  getSort (label) {
    if (productSorts.indexOf(label) > -1) {
      console.log(`分類: ${label}`)
    } else {
      console.log('不存在')
    }
  }
}
export default productAllSort;

此時,如果需要更動這個模組,添加新的商品種類進去 productSorts 中,讓 productAllSort 中獲取商品陣列中特定分類的功能(getSort)可以取得,如何在盡量不修改程式邏輯的方式,將此邏輯添加進去?
舉例而言,更改後程式碼

let productSorts = ['3C產品', '食品'];
let productAllSort = {
  getSort (label) {
    if (productSorts.indexOf(label) > -1) {
      console.log(`分類: ${label}`)
    } else {
      console.log('不存在')
    }
  },
  addSort (label) {
    productSorts.push(label);
  }
}
export default productAllSort;

此時通過添加addSort()這項方法就可以解決這項問題,而且用更改到舊有的程式碼,滿足了開放封閉的原則。

Liskov替換原則 (LSP,Liskov Substitution Principle)

LSP 原則規範的是繼承的關係,子物件必須可以替換父物件,而子物件可以擴展父物件功能,但不會改變父物件原有功能

舉例而言:

class Rectangle {
  constructor(width, height) {
    this._width = width;     // _ 在 js 中默認是私有變數,僅存在在 Component 內部
    this._height = height;
  }
  get area() {
    return this._width*this._height;
  }
}
class Square extends Rectangle {
  constructor(width) {
    super(width, width);
  }
}
const rectangle = new Rectangle(3,9);
console.log(rectangle.area);  // 27
const square = new Square(4);
console.log(square.area);  // 16

這裏選擇用 Rectangle 當作父物件是因為計算面積方法一樣(長 * 寬),而 Rectangle 長寬未必相同,因此一定需要兩個變數,而 Square 長寬相同,只要覆寫父的長和寬的值就行了。
這裏通過繼承父物件來完成新功能,雖然寫起來簡單,但是繼承後的元件可復用性會比較差,不同於OOP的語言,React 沒有像是 interface 的功能,所以像是這種繼承父物件的方法有時候子物件反而會多了很多不必要的程式碼(繼承而來),因此筆者要提醒這種方法要慎用、並且慎選父物件。

在 React 中,大部分是靠父元素傳遞 props 給子元素,而不是依靠繼承關係,所以自然就形成了對於 Component 功能的保護,不容易被修改破壞。例如:<MyButton> 繼承於 <Button>這個原生元件,除了去擴展它的樣式(像是顏色、點擊後的功能等),但並不會破壞掉它是button這件事(代表它還是有button該有的功能),而這就是Liskov替換原則在這裡的意義。

結論

  • 初略介紹 SOLID
  • 介紹了 SRP, OCP, LSP

/images/emoticon/emoticon18.gif要開始燃燒了~衝刺


上一篇
【Day 23】React 關於 Hook(3)
下一篇
【Day 25】用 SOLID 方式開發 React (2)
系列文
玩轉 React 從0到130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言